package per.esmool;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;

import com.alibaba.fastjson.JSONObject;
import com.jfinal.core.JFinal;
import com.jfinal.plugin.redis.Cache;
import com.jfinal.plugin.redis.Redis;

@SuppressWarnings("deprecation")
public class RedisCachedSession implements HttpSession {
	
	public static String REDIS_SESSION_DB_NAME = "rcs_session";
	public static int SESSION_TIMEOUT = 120000;
	
	private String dbName;
	private String id;
	private long creationTime;
	private long lastAccessedTime;
	private int maxInactiveInterval;
	private boolean isNew = false;
	
	private boolean invalidate = false;
	
	private ServletContext servletContext;
	private HttpSessionContext sessionContext;
	
	private HttpSession localSession;
	
	Map<String, Object>
		attributes = new HashMap<>(),
		values = new HashMap<>();
		
	public RedisCachedSession(String dbName, String id, int maxInactiveInterval, HttpSession localSession) {
		this.dbName = dbName;
		this.id = id;
		this.maxInactiveInterval = maxInactiveInterval;
		this.localSession = localSession;
		this.servletContext = this.localSession.getServletContext();
		this.sessionContext = this.localSession.getSessionContext();
		this.load();
	}
	
	private void loadFromJSONObject(JSONObject json) {
		this.dbName = json.getString("dbName");
		this.id = json.getString("id");
		this.creationTime = json.getLongValue("creationTime");
		this.lastAccessedTime = json.getLongValue("lastAccessedTime");
		this.maxInactiveInterval = json.getIntValue("maxInactiveInterval");
		this.isNew = json.getBooleanValue("isNew");
		
		JSONObject attributes = json.getJSONObject("attributes");
		JSONObject values = json.getJSONObject("values");
		
		this.attributes.clear();
		attributes.forEach((k, v) -> this.attributes.put(k, v));
		
		this.values.clear();
		values.forEach((k, v) -> this.values.put(k, v));
		
		if (JFinal.me().getConstants().getDevMode())
			System.out.println("Session loaded: " + json.toJSONString());
	}
	
	private JSONObject saveToJSONObject() {
		this.lastAccessedTime = System.currentTimeMillis();
			
		JSONObject attributes = new JSONObject();
		this.attributes.forEach((k, v) -> attributes.put(k, v));
		
		JSONObject values = new JSONObject();
		this.values.forEach((k, v) -> values.put(k, v));
		
		JSONObject json = new JSONObject();
		
		json.put("dbName", this.dbName);
		json.put("id", this.id);
		json.put("creationTime", this.creationTime);
		json.put("lastAccessedTime", this.lastAccessedTime);
		json.put("maxInactiveInterval", this.maxInactiveInterval);
		json.put("isNew", this.isNew);
		json.put("attributes", attributes);
		json.put("values", values);
		
		if (JFinal.me().getConstants().getDevMode())
			System.out.println("Session cached: " + json.toJSONString());
		
		return json;
	}
	
	public void load() {
		// 1. check if session not cached in redis
		//		create new cache
		// 2. load the cache
		
		if (this.invalidate)
			return;
		
		Cache cache = Redis.use(this.dbName);
		
		JSONObject bean = null;
		if (!cache.exists(this.id)) {
			this.isNew = true;
			this.creationTime = System.currentTimeMillis();
			this.save();
			return;
		}
		
		bean = cache.get(this.id);
		this.loadFromJSONObject(bean);
		this.isNew = false;
		this.save();
	}
	
	public void save() {
		// save this session into cache in redis
		if (this.invalidate)
			return;
		
		Cache cache = Redis.use(this.dbName);
		
		this.lastAccessedTime = System.currentTimeMillis();
		
		JSONObject bean = this.saveToJSONObject();
		cache.set(this.id, bean);
		cache.pexpire(this.id, this.maxInactiveInterval);
	}
	
	public HttpSession getLocalSession() {
		return this.localSession;
	}

	@Override
	public Object getAttribute(String key) {
		if (this.invalidate)
			return null;
		
		return this.attributes.get(key);
	}

	@Override
	public Enumeration<String> getAttributeNames() {
		if (this.invalidate)
			return null;
		
		Iterator<String> itr = this.attributes.keySet().iterator();
		return new Enumeration<String>() {

			@Override
			public boolean hasMoreElements() {
				return itr.hasNext();
			}

			@Override
			public String nextElement() {
				return itr.next();
			}
			
		};
	}

	@Override
	public long getCreationTime() {
		if (this.invalidate)
			return 0;
		
		return this.creationTime;
	}

	@Override
	public String getId() {
		if (this.invalidate)
			return null;
		
		return this.id;
	}

	@Override
	public long getLastAccessedTime() {
		if (this.invalidate)
			return 0;
		
		return this.lastAccessedTime;
	}

	@Override
	public int getMaxInactiveInterval() {
		if (this.invalidate)
			return 0;
		
		return this.maxInactiveInterval;
	}

	@Override
	public ServletContext getServletContext() {
		if (this.invalidate)
			return null;
		
		return this.servletContext;
	}

	@Override
	public HttpSessionContext getSessionContext() {
		if (this.invalidate)
			return null;
		
		return this.sessionContext;
	}

	@Override
	public Object getValue(String key) {
		if (this.invalidate)
			return null;
		
		return this.values.get(key);
	}

	@Override
	public String[] getValueNames() {
		if (this.invalidate)
			return null;
		
		return this.values.keySet().toArray(new String[0]);
	}

	@Override
	public void invalidate() {
		Cache cache = Redis.use(this.dbName);
		
		cache.del(this.id);
		
		this.attributes.clear();
		this.values.clear();
		
		this.invalidate = true;
	}

	@Override
	public boolean isNew() {
		if (this.invalidate)
			return false;
		
		return this.isNew;
	}

	@Override
	public void putValue(String key, Object value) {
		if (this.invalidate)
			return;
		
		this.values.put(key, value);
	}

	@Override
	public void removeAttribute(String key) {
		if (this.invalidate)
			return;
		
		this.attributes.remove(key);
	}

	@Override
	public void removeValue(String key) {
		if (this.invalidate)
			return;
		
		this.values.remove(key);
	}

	@Override
	public void setAttribute(String key, Object value) {
		if (this.invalidate)
			return;
		
		this.attributes.put(key, value);
	}

	@Override
	public void setMaxInactiveInterval(int interval) {
		if (this.invalidate)
			return;
		
		this.maxInactiveInterval = interval;
	}

}
